package cz.muni.fi.pv112.cranesimulator.scene.handlers;

import cz.muni.fi.pv112.cranesimulator.models.Model;
import cz.muni.fi.pv112.cranesimulator.models.crane.Crane;
import cz.muni.fi.pv112.cranesimulator.models.crane.LiftingZone;
import cz.muni.fi.pv112.cranesimulator.models.crane.parts.LiftingRod;
import cz.muni.fi.pv112.cranesimulator.models.crane.parts.Magnet;
import cz.muni.fi.pv112.cranesimulator.models.scene.Container;
import cz.muni.fi.pv112.cranesimulator.scene.Scene;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import static cz.muni.fi.pv112.cranesimulator.utils.Calculator.recalculateCoordinatesToSceneCoordinateSystem;
import static cz.muni.fi.pv112.cranesimulator.utils.handlers.opengl.DisplayListHandler.deleteDiplayLists;
import static cz.muni.fi.pv112.cranesimulator.utils.handlers.opengl.DisplayListHandler.storeOBJModelsToDiplayList;
import static cz.muni.fi.pv112.cranesimulator.utils.handlers.opengl.ModelDisplayHandler.drawModelFromDisplaylist;
import static java.lang.Math.abs;
import static java.lang.Math.sqrt;

/**
 *
 * @author Michal Ďuriš
 */
public class CraneHandler {

    private Crane crane;

    public CraneHandler() {
    }

    public void init(GL gl) {
        crane = new Crane();
        crane.init(gl);

        storeCraneToDisplayList(gl);
    }

    public Crane getCrane() {
        return crane;
    }

    private void storeCraneToDisplayList(GL gl) {
        storeOBJModelsToDiplayList(gl.getGL2(), crane.getCraneParts(), crane
                .getTexture());
    }

    public void deleteCraneFromDisplayList(GL gl) {
        deleteDiplayLists(gl.getGL2(), crane.getCraneParts());
    }

    public void rotateLeftOperatingPart() {
        crane.getOperatingPart().increaseRotaryAngle();
    }

    public void rotateRightOperatingPart() {
        crane.getOperatingPart().decreaseRotaryAngle();
    }

    public float moveLiftingRiderForward() {
        float executedShift = crane.getLiftingRider().increaseShift();

        if (executedShift != 0.0f) {
        }

        return executedShift;
    }

    public float moveLiftingRiderBackward() {
        float executedShift = crane.getLiftingRider().decreaseShift();

        if (executedShift != 0.0f) {
        }

        return executedShift;
    }

    public float moveDownLiftingRod() {
        float liftedObjectHeight = 0.0f;

        if (crane.isLiftingObject()) {
            liftedObjectHeight = crane.getLiftedObject().getHeight();
        }

        if (isRodMoveEnabled(crane.getLiftingRod().getExtension() + crane
                .getLiftingRod().getExtensionStep(), liftedObjectHeight)) {
            crane.getLiftingRod().increaseExtension();

            float newLengthIncrease = calculateRodLengthIncrease(crane
                    .getLiftingRod().getExtension());
            float lengthChange = newLengthIncrease - crane.getLiftingRod()
                    .getLengthIncrease();

            crane.getLiftingRod().setTopBaseVerticalIncrease(
                    calculateRodTopBaseVerticalIncrease(crane.getLiftingRod()
                            .getExtension()));
            crane.getLiftingRod().setLengthIncrease(newLengthIncrease);

            crane.getMagnet().moveCatchingZoneCenterPointBy(0.0f, -lengthChange,
                    0.0f);

            return lengthChange;
        }

        return 0.0f;
    }

    public float moveUpLiftingRod() {
        if (crane.getLiftingRod().decreaseExtension()) {
            float newLengthIncrease = calculateRodLengthIncrease(crane
                    .getLiftingRod().getExtension());
            float lengthChange = crane.getLiftingRod().getLengthIncrease()
                    - newLengthIncrease;

            crane.getLiftingRod().setTopBaseVerticalIncrease(
                    calculateRodTopBaseVerticalIncrease(crane.getLiftingRod()
                            .getExtension()));
            crane.getLiftingRod().setLengthIncrease(newLengthIncrease);

            crane.getMagnet().moveCatchingZoneCenterPointBy(0.0f, lengthChange,
                    0.0f);

            return lengthChange;
        }

        return 0.0f;
    }

    public void catchContainer(Container[] containers) {
        if (containers != null) {
            if (isMagnetInCatchHeight(containers, crane.getMagnet()
                    .getCatchingZoneHeight())) {
                updateCatchingZoneCenterPointCurrentPosition();

                Container catchedContainer = getCatchedObject(containers,
                        crane.getMagnet()
                        .getCatchingZoneCenterPointCurrentPosition());
                if (catchedContainer != null) {
                    crane.setLiftingObject(true);
                    crane.setLiftedObject(catchedContainer);
                }
            }
        }
    }

    public void releaseContainer() {
        if (!isRodMoveEnabled(crane.getLiftingRod().getExtension() + crane
                .getLiftingRod().getExtensionStep(), crane.getLiftedObject()
                .getHeight())) {
            //Dumm correction because container-magnet colision is not implemented
            ((Model) crane.getLiftedObject()).getDisplacement()[Scene.Y_AXIS] = 0.0f;

            crane.setLiftedObject(null);
            crane.setLiftingObject(false);
        }
    }

    public boolean isLiftingContainer() {
        return crane.isLiftingObject();
    }

    public Container getLiftedContainer() {
        return (Container) crane.getLiftedObject();
    }

    public void displayCrane(GL2 gl) {
        gl.glPushMatrix();

        drawModelFromDisplaylist(gl, crane.getCarrierPart());

        gl
                .glRotatef(crane.getOperatingPart().getRotaryAngle(), 0.0f, 1.0f,
                        0.0f);
        drawModelFromDisplaylist(gl, crane.getOperatingPart());

        gl.glTranslatef(-crane.getLiftingRider().getShift(), 0.0f, 0.0f);
        drawModelFromDisplaylist(gl, crane.getLiftingRider());

        gl.glPushMatrix();

        gl.glTranslatef(0.0f, -crane.getLiftingRod()
                .getTopBaseVerticalIncrease(), 0.0f);
        gl.glScalef(1.0f, crane.getLiftingRod().getExtension(), 1.0f);
        drawModelFromDisplaylist(gl, crane.getLiftingRod());

        gl.glPopMatrix();

        gl.glTranslatef(0.0f, -crane.getLiftingRod().getLengthIncrease(), 0.0f);
        drawModelFromDisplaylist(gl, crane.getMagnet());

        gl.glPopMatrix();
    }

    private static float calculateRodTopBaseVerticalIncrease(float extension) {
        return LiftingRod.TOP_BASE * extension - LiftingRod.TOP_BASE;
    }

    private static float calculateRodLengthIncrease(float extension) {
        return LiftingRod.LENGTH_WITH_NO_EXTENSION * extension
                - LiftingRod.LENGTH_WITH_NO_EXTENSION;
    }

    private void updateCatchingZoneCenterPointCurrentPosition() {
        float[] newPosition = recalculateCoordinatesToSceneCoordinateSystem(
                Magnet.CATCHING_ZONE_CENTER_POINT_START_POSITION[Scene.X_AXIS]
                - crane.getLiftingRider().getShift(),
                Magnet.CATCHING_ZONE_CENTER_POINT_START_POSITION[Scene.Y_AXIS],
                Magnet.CATCHING_ZONE_CENTER_POINT_START_POSITION[Scene.Z_AXIS],
                new float[] {0.0f, -crane.getOperatingPart().getRotaryAngle(),
                             0.0f});

        crane.getMagnet().moveCatchingZoneCenterPointTo(
                newPosition[Scene.X_AXIS],
                crane.getMagnet().getCatchingZoneHeight(),
                newPosition[Scene.Z_AXIS]);
    }

    private static float calculateLengthBetweenCenterPoints(
            float[] liftingZoneCenterPoint, float[] catchingZoneCenterPoint) {
        double vectorX = catchingZoneCenterPoint[Scene.X_AXIS]
                - liftingZoneCenterPoint[Scene.X_AXIS];
        double vectorZ = catchingZoneCenterPoint[Scene.Z_AXIS]
                - liftingZoneCenterPoint[Scene.Z_AXIS];

        return (float) sqrt(vectorX * vectorX + vectorZ * vectorZ);
    }

    private static boolean isMagnetInCatchHeight(Container[] containers,
                                                 float magnetHeight) {
        for (Container container : containers) {

            if (magnetHeight <= (container.getHeight() + 1.0f)) {
                return true;
            }
        }

        return false;
    }

    private static Container getCatchedObject(Container[] containers,
                                              float[] magnetCatchingZonePosition) {
        for (Container container : containers) {
            for (LiftingZone liftingZone : container.getLiftingZones()) {

                if (calculateLengthBetweenCenterPoints(liftingZone
                        .getCurrentPosition(), magnetCatchingZonePosition)
                        <= liftingZone.getRadius() + Magnet.MAGNET_RADIUS) {
                    return container;
                }
            }
        }

        return null;
    }

    private static boolean isRodMoveEnabled(float extension,
                                            float liftedObjectHeight) {

        return LiftingRod.BOTTOM_BASE * extension - abs(
                calculateRodTopBaseVerticalIncrease(extension))
                - Magnet.MAGNET_HEIGHT - liftedObjectHeight > 0.0f;
    }

}
